using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Security.Cryptography;
using Intuipic.Data;
using Kent.Boogaart.HelperTrinity;

namespace Intuipic
{
	/// <summary>
	/// Enumerates files on behalf of the <see cref="MainWindow"/>.
	/// </summary>
	public sealed class FileEnumerator : INotifyPropertyChanged
	{
		private IList<string> _files;
		private int? _lastSelectedFileIndex;

		private readonly IList<string> _history;
		private int? _selectedHistoryIndex;
		
		private readonly RandomNumberGenerator _rng;
		private readonly byte[] _randomBytes;

		private const int _maxHistorySize = 2048;

		/// <summary>
		/// Gets the currently selected file.
		/// </summary>
		/// <value>
		/// The full path of the currently selected file, or <see langword="null"/> if there is no selected file.
		/// </value>
		public string Current
		{
			get
			{
				if (!_selectedHistoryIndex.HasValue)
				{
					return null;
				}
				else
				{
					return _history[_selectedHistoryIndex.Value];
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether there are any files being enumerated by this <c>FileEnumerator</c>.
		/// </summary>
		public bool HasFiles
		{
			get
			{
				return ((_files != null) && (_files.Count > 0));
			}
		}

		private DisplayOrder DisplayOrder
		{
			get
			{
				return Settings.Current.DisplayOrder;
			}
		}

		/// <summary>
		/// Occurs whenever a property on this object changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Constructs an instance of <c>FileEnumerator</c>.
		/// </summary>
		public FileEnumerator()
		{
			_history = new List<string>();
			_rng = RandomNumberGenerator.Create();
			_randomBytes = new byte[4];
		}

		/// <summary>
		/// Clears all history.
		/// </summary>
		public void ClearHistory()
		{
			_history.Clear();
			_selectedHistoryIndex = null;
			_lastSelectedFileIndex = null;
		}

		/// <summary>
		/// Assigns the specified files as candidates for selection.
		/// </summary>
		public void SetFiles(IEnumerable<string> files)
		{
			_files = new List<string>(files);

			if (_history.Count > 0)
			{
				//no file has yet been selected from this file set
				_lastSelectedFileIndex = null;
				//move to the end of history whenever the files change so we start selecting the new files
				_selectedHistoryIndex = _history.Count - 1;
			}

			OnPropertyChanged("HasFiles");
		}

		/// <summary>
		/// Selects the next file in sequence.
		/// </summary>
		public bool SelectNext()
		{
			if ((_files == null) || (_files.Count == 0))
			{
				//no files from which to select
				return false;
			}
			
			bool needNewHistoryItem = !_selectedHistoryIndex.HasValue || (_selectedHistoryIndex.Value == _history.Count - 1);

			if (needNewHistoryItem)
			{
				//select file and add to end of history
				switch (DisplayOrder)
				{
					case DisplayOrder.Ascending:
						if (!_lastSelectedFileIndex.HasValue || (_lastSelectedFileIndex == 0))
						{
							_lastSelectedFileIndex = _files.Count - 1;
						}
						else
						{
							_lastSelectedFileIndex = _lastSelectedFileIndex.Value - 1;
						}

						break;
					case DisplayOrder.Descending:
						if (!_lastSelectedFileIndex.HasValue || (_lastSelectedFileIndex == (_files.Count - 1)))
						{
							_lastSelectedFileIndex = 0;
						}
						else
						{
							_lastSelectedFileIndex = _lastSelectedFileIndex.Value + 1;
						}

						break;
					case DisplayOrder.Random:
						_lastSelectedFileIndex = GetRandomNumber(_files.Count);
						break;
					default:
						Debug.Assert(false);
						break;
				}

				AddAndSelectHistoryEntry(_files[_lastSelectedFileIndex.Value], HistoryLocation.End);
			}
			else
			{
				//just need to move forward in history
				_selectedHistoryIndex += 1;
			}

			//since the history index changed, so has the current file
			OnPropertyChanged("Current");
			return true;
		}

		/// <summary>
		/// Selects the previous file in sequence.
		/// </summary>
		public bool SelectPrevious()
		{
			if ((_files == null) || (_files.Count == 0))
			{
				//no files from which to select
				return false;
			}

			bool needNewHistoryItem = !_selectedHistoryIndex.HasValue || (_selectedHistoryIndex.Value == 0);

			if (needNewHistoryItem)
			{
				//select file and add to end of history
				switch (DisplayOrder)
				{
					case DisplayOrder.Ascending:
						if (!_lastSelectedFileIndex.HasValue || (_lastSelectedFileIndex == (_files.Count - 1)))
						{
							_lastSelectedFileIndex = 0;
						}
						else
						{
							_lastSelectedFileIndex = _lastSelectedFileIndex.Value + 1;
						}

						break;
					case DisplayOrder.Descending:
						if (!_lastSelectedFileIndex.HasValue || (_lastSelectedFileIndex == 0))
						{
							_lastSelectedFileIndex = _files.Count - 1;
						}
						else
						{
							_lastSelectedFileIndex = _lastSelectedFileIndex.Value - 1;
						}

						break;
					case DisplayOrder.Random:
						_lastSelectedFileIndex = GetRandomNumber(_files.Count);
						break;
					default:
						Debug.Assert(false);
						break;
				}

				AddAndSelectHistoryEntry(_files[_lastSelectedFileIndex.Value], HistoryLocation.Start);
			}
			else
			{
				//just need to move backward in history
				_selectedHistoryIndex -= 1;
			}

			OnPropertyChanged("Current");
			return true;
		}

		private void AddAndSelectHistoryEntry(string file, HistoryLocation historyLocation)
		{
			Debug.Assert(file != null);

			switch (historyLocation)
			{
				case HistoryLocation.Start:
					_history.Insert(0, file);
					break;
				case HistoryLocation.End:
					_history.Add(file);
					break;
				default:
					Debug.Assert(false);
					break;
			}

			//keep the history from growing too big
			if (_history.Count > _maxHistorySize)
			{
				switch (historyLocation)
				{
					case HistoryLocation.Start:
						_history.RemoveAt(_history.Count - 1);
						break;
					case HistoryLocation.End:
						_history.RemoveAt(0);
						break;
				}
			}

			//select the new history item
			switch (historyLocation)
			{
				case HistoryLocation.Start:
					_selectedHistoryIndex = 0;
					break;
				case HistoryLocation.End:
					_selectedHistoryIndex = _history.Count - 1;
					break;
				default:
					Debug.Assert(false);
					break;
			}
		}

		private int GetRandomNumber(int max)
		{
			//get a more random number than System.Random can do for us
			_rng.GetBytes(_randomBytes);
			int randomNumber = Math.Abs(BitConverter.ToInt32(_randomBytes, 0));
			return randomNumber % max;
		}

		private void OnPropertyChanged(string propertyName)
		{
			Debug.Assert(propertyName != null);
			EventHelper.Raise(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));
		}

		private enum HistoryLocation
		{
			Start,
			End
		}
	}
}
